/********************************************************************************
  DESCRIPTION OF THE FILE HERE
********************************************************************************/

#include "thread_list.h"
#include "common.h"

#include <sys/socket.h>
#include <pthread.h>
#include <semaphore.h>
#include "request.h"
#include "response.h"

/* questo e' il mutex che sta a proteggere la lista nel mentre che viene cambiata
   di struttura dai multipli concorrenti accessi (tutto e' nato a colpa dei fprintf).
 */
static sem_t mutex_sem;

typedef struct SThreadList {  
  pthread_t pt;
  ptrRequest request; /* assumera' diversi valori nel corso dell'esecuzione */
  bool is_busy;

  /* socket descriptor (se != -1 e' supposto corrispondere ad una socket aperta)
   * del client che e' in attesa di notifica in RPC
   */
  int client_sd;

  /* semaforo utilizzato per svegliare il thread pt quando gli viene assegnato un compito */
  sem_t priv_sem;

  /* semaforo utilizzato per indicare al thread client quando il processing e' terminato*/
  sem_t end;

  ptrThreadList prev;
  ptrThreadList next;
  /* seguono i puntatori agli elementi prev e next delle liste
   * busy o free. Ogni elemento della lista, si ricorda, sta
   * o in busy o in free in maniera esclusiva. Questi puntatori
   * permettono dunque di muoversi nell'una o nell'altra.
   */
  ptrThreadList inner_prev;
  ptrThreadList inner_next;
  /* interessanti solo per il primo elemento (punto d'accesso) */
  int size;
  ptrThreadList tail; /* ultimo elemento della lista */
  ptrThreadList busy; /* task operanti */
  ptrThreadList free; /* task liberi */
} ThreadList;

static bool isMotherElem(ptrThreadList tlist){
  bool res;
  sem_wait(&mutex_sem); {
    res = (tlist->pt == -1)
      && (tlist->busy != NULL)
      && (tlist->free != NULL);
  } sem_post(&mutex_sem);
  return res;
}

static void cleanup_fun(void *arg)
{
  ptrThreadList elem = (ptrThreadList)arg;
  ThreadList_destroy(elem);
  track_thread("THREAD KILLED");  
}

/* Thread assegnato insieme alla creazione dell'elemento della lista.
 * Resta dormiente fino a quando non gli viene assegnato un compito,
 * poi lo esegue.
 * Lui e' anche responsabile dell'invio del risultato al client e
 * di chiudere la socket con il client.
 */
static void * basethread(void * arg){
  int err;
  ptrResponse response;
  char * buf;
  ptrThreadList elem = (ptrThreadList)arg;
  pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
  pthread_cleanup_push(cleanup_fun, arg);
  while(true){
    track_thread("I will put myself in wait now");
    /* quando priv_sem varra' 1 la request sara' valida */
    sem_wait(&elem->priv_sem);
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
    track_thread("I'VE BEEN WOKEN UP");
    debug("\nHEY! SOMEONE WOKE ME UP! WHO'S THIS FUCKING ASS?!?\n");
    debug("---------------------------------------------------------------\n");
    debug("LET'S PRINTF THE REQUEST AFTER THE REQUEST FIELD GETS VALID IN basethread:\n");
#ifdef DEBUG
    Request_printf(elem->request);
#endif
    debug("---------------------------------------------------------------\n\n");
    response = Request_perform(elem->request);
    track_thread("I GOT MY RESPONSE SO I SIGNAL MY COMPLETION");
    debug("I DID MY JOB. I'M GONNA SLEEP NOW AND SHOULD NOBODY INTERRUPT MY SLEEP.\n");

    track_thread("I'M GONNA SEND THE RESULT ON THE PORT");
    buf = Response_toDataStream(response);
    /* la send e' bloccante */
    err = send(elem->client_sd, buf, RESPONSE_DATA_SIZE, 0);
    throw_cond_error((err < 0),
		     "basethread",
		     "Error while sending data"
		     );
    track_thread("RESULT SENT BACK");
    throw_cond_error((close(elem->client_sd) < 0),
		     "basethread",
		     "Error in connection closing"
		     );
    track_thread("MY TASK IS COMPLETED SO I BLOCK ON priv_sem AGAIN");
    sem_post(&elem->end);
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
  }
  track_thread("I AM ABOUT TO DIE NOW");
  pthread_cleanup_pop(0);
  return NULL;
}

static void ThreadList_init(ptrThreadList tlist){  
  throw_cond_error(((sem_init(&mutex_sem, 0, 1)) < 0),
		   "ThreadList_init",
		   "Error in sem_init"
		   );;
  /* Questo rappresenta il punto d'accesso alla lista, non vi sono
   * elementi ancora.
   */

  sem_wait(&mutex_sem); {
    tlist->pt = -1;
    tlist->request = NULL;
    tlist->is_busy = false;
    tlist->client_sd = -1;
    /* tlist->priv_sem = NULL; */
    /* tlist->end = NULL; */
    tlist->prev = NULL;
    tlist->next = NULL;
    tlist->inner_prev = NULL;
    tlist->inner_next = NULL;
    tlist->size = 0;
    tlist->tail = tlist;

    tlist->busy = new(ThreadList);
    tlist->busy->pt = -1;
    tlist->busy->request = NULL;
    tlist->busy->is_busy = false; /* siamo solo sulla prima occorrenza (access point) */
    tlist->busy->client_sd = -1;
    /* tlist->busy->priv_sem = NULL; */
    /* tlist->busy->end = NULL; */
    /* tlist->busy->mutex_sem = NULL; */
    tlist->busy->prev = NULL;
    tlist->busy->next = NULL;
    tlist->busy->inner_prev = NULL;
    tlist->busy->inner_next = NULL;
    tlist->busy->size = 0;
    tlist->busy->tail = tlist->busy;
    tlist->busy->busy = NULL;
    tlist->busy->free = NULL;

    tlist->free = new(ThreadList);
    tlist->free->pt = -1;
    tlist->free->request = NULL;
    tlist->free->is_busy = false;
    tlist->free->client_sd = -1;
    /* tlist->free->priv_sem = NULL; */
    /* tlist->free->end = NULL; */
    /* tlist->free->mutex_sem = NULL; */
    tlist->free->prev = NULL;
    tlist->free->next = NULL;
    tlist->free->inner_prev = NULL;
    tlist->free->inner_next = NULL;
    tlist->free->size = 0;
    tlist->free->tail = tlist->free;
    tlist->free->busy = NULL;
    tlist->free->free = NULL;
  } sem_post(&mutex_sem);
}

ptrThreadList ThreadList_create(){
  ptrThreadList tlist = new(ThreadList);
  ThreadList_init(tlist);
  return tlist;
}

static bool everyoneBusy(ptrThreadList tlist){
  bool res;
  sem_wait(&mutex_sem); {
    res = (tlist->free->size == 0);
  } sem_post(&mutex_sem);
  return res;
}

void ThreadList_addNewThread(ptrThreadList tlist){
  int err;
  if (tlist == NULL) throw_error("ThreadList_addNewThread", "null thread list");

  ptrThreadList elem = new(ThreadList);
  pthread_create(&elem->pt, NULL, basethread, (void *)elem);
  elem->request = NULL;
  elem->is_busy = false;
  elem->client_sd = -1;
  err = sem_init(&elem->priv_sem, 0, 0);
  throw_cond_error((err < 0),
                   "ThreadList_init",
                   "Error in sem_init"
                   );
  err = sem_init(&elem->end, 0, 0);
  throw_cond_error((err < 0),
                   "ThreadList_init",
                   "Error in sem_init"
                   );

  sem_wait(&mutex_sem); {
    elem->next = NULL;
    elem->prev = tlist->tail;
    tlist->tail->next = elem;
    tlist->tail = tlist->tail->next;
    tlist->size = tlist->size + 1;

    elem->inner_next = NULL;
    elem->inner_prev = tlist->free->tail;
    tlist->free->tail->inner_next = elem;
    tlist->free->tail = tlist->free->tail->inner_next;
    tlist->free->size = tlist->free->size + 1;
  } sem_post(&mutex_sem);
}

void ThreadList_assignSendAndCloseConnection(ptrThreadList tlist, ptrRequest r, int client_sd){
  ptrThreadList previous, elem, following;
  if (tlist == NULL) throw_error("ThreadList_addNewThread", "null thread list");
  if (r == NULL) throw_error("ThreadList_addNewThread", "null request");

  debug("I ENTERED ThreadList_assignSendAndCloseConnection. IT FOLLOWS THE REQUEST:\n\t");
#ifdef DEBUG
  Request_printf(r);
#endif

  if (everyoneBusy(tlist)){
    ThreadList_addNewThread(tlist);
  }

  sem_wait(&mutex_sem); {
    /* preleva un elemento dalla lista dei thread non attivi */
    elem = tlist->free->tail;
    tlist->free->tail = tlist->free->tail->inner_prev;
    tlist->free->size = tlist->free->size - 1;

    /* inserisci l'elemento nella lista dei thread attivi */
    elem->is_busy = true;
    elem->client_sd = client_sd;
    elem->inner_prev = tlist->busy->tail;
    elem->inner_next = NULL;
    tlist->busy->tail->inner_next = elem;
    tlist->busy->tail = tlist->busy->tail->inner_next;
    tlist->busy->size = tlist->busy->size + 1;
  } sem_post(&mutex_sem);

  /* svolgi il lavoro a cui sei stato posto */
  elem->request = r;
  sem_post(&elem->priv_sem);
  debug("\nI WOKE UP THE THREAD ON ITS TASK NOW AND WAIT ON ITS COMPLETION\n");
  /* qui il thread relativo svolgera' le operazioni richieste in elem->request */
  sem_wait(&elem->end);

  sem_wait(&mutex_sem); {
    /* togli di nuovo l'elemento dalla lista dei busy */
    previous = elem->prev;
    following = elem->next;
    elem->inner_prev->inner_next = following;
    if (elem->inner_next != NULL) elem->inner_next->inner_prev = previous;
    tlist->busy->size = tlist->busy->size - 1;
  
    /* reinserisci l'elemento nella lista dei thread non attivi */
    elem->is_busy = false;
    elem->client_sd = -1;
    elem->inner_prev = tlist->free->tail;
    elem->inner_next = NULL;
    tlist->free->tail->inner_next = elem;
    tlist->free->tail = tlist->free->tail->inner_next;
    tlist->free->size = tlist->free->size + 1;
  } sem_post(&mutex_sem);
}

void ThreadList_cutOffThread(ptrThreadList tlist, int how_many){
  int i = 0;
  ptrThreadList iter, previous, following;
  if (tlist == NULL) throw_error("ThreadList_cutOffThread", "null thread list");
  if (how_many <= 0) throw_error("ThreadList_cutOffThread", "wrong cutoff request");

  sem_wait(&mutex_sem); {
    while ((!tlist->free->size == 0) && (i != how_many)){
      /* cancella un elemento dalla lista dei thread liberi
       * (solo se ce ne sono di liberi e' possibile ridurre la lista)
       */
      iter = tlist->free->tail;
      tlist->free->tail = tlist->free->tail->inner_prev;
      tlist->free->size = tlist->free->size - 1;

      /* cancella quell'elemento dalla lista madre */
      previous = iter->prev;
      following = iter->next;
      iter->prev->next = following;
      if (iter->next != NULL) iter->next->prev = previous;
      tlist->size = tlist->size - 1;

      /* deinizializza tutti i campi e dealloca l'elemento */
      ThreadList_destroy(iter);

      i++;
    }
  } sem_post(&mutex_sem);
}

int ThreadList_getSize(ptrThreadList tlist){
  return tlist->size;
}

void ThreadList_fprintfStatistics(ptrThreadList tlist, FILE * logfile){
  char * separator = 
    "--------------------------------------------------------------------------------";
  int total_n; /* total number of threads */
  int active_n; /* number of active threads */
  int free_n; /* number of free threads */
  time_t now; 
  if (tlist == NULL) throw_error("Pool_fprintfStatistics", "null pool");
  if (!isMotherElem(tlist)) throw_error("Pool_fprintfStatistics", "not a mother elem");
  if (logfile == NULL) throw_error("Pool_fprintfStatistics", "null log file");

  sem_wait(&mutex_sem);
  /* inside the mutex, just copy information you need.
   * fprintf will be performed next becouse it would slow the system otherwise.
   */
  now = time(NULL);
  total_n = tlist->size;
  active_n = tlist->busy->size;
  free_n = tlist->free->size;
  sem_post(&mutex_sem);

  fprintf(logfile, "%s\n", separator);
  fprintf(logfile, "Log time: %s\n", ctime(&now));
  fprintf(logfile, "\tTotal number of threads:  %d\n", total_n);
  fprintf(logfile, "\tNumber of active threads: %d\n", active_n);
  fprintf(logfile, "\tNumber of free threads:   %d\n", free_n);
  fprintf(logfile, "%s\n", separator);
}

#ifdef DEBUG
void ThreadList_printf (ptrThreadList tlist){
  /* BLA, BLA, BLA */
}
#endif /* DEBUG */

static void ThreadList_deinit(ptrThreadList tlist){
  if (isMotherElem(tlist)){    

    sem_wait(&mutex_sem); {
      if (tlist->busy->size != 0)
	throw_error("ThreadList_deinit", "lista di thread attivi non vuota");
    } sem_post(&mutex_sem);

    /* ferma tutti i thread rimuovendo tutti gli elementi dalla lista */
    ThreadList_cutOffThread(tlist, tlist->size);

    /* deinizializza l'access point */
    sem_destroy(&mutex_sem);
    delete(tlist->busy);
    delete(tlist->free);
  } else {    
    throw_cond_error((tlist->is_busy),
		     "ThreadList_deinit",
		     "Impossibile cancellare un thread operativo");
    pthread_cancel(tlist->pt);
    pthread_join(tlist->pt, NULL);
    sem_destroy(&tlist->priv_sem);
    sem_destroy(&tlist->end);
  }
};

void ThreadList_destroy(ptrThreadList tlist){
  if (tlist != NULL){
    ThreadList_deinit(tlist);
    delete(tlist);
  }
}
